﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

// https://github.com/CommunityToolkit/dotnet/blob/main/CommunityToolkit.HighPerformance/Extensions/BoolExtensions.cs

using System.Runtime.CompilerServices;

namespace FisheryLib;

/// <summary>
/// Helpers for working with the <see cref="bool"/> type.
/// </summary>
[PublicAPI]
public static class BoolExtensions
{
	/// <summary>
	/// Converts the given <see cref="bool"/> value into a <see cref="byte"/>.
	/// </summary>
	/// <param name="flag">The input value to convert.</param>
	/// <returns>1 if <paramref name="flag"/> is <see langword="true"/>, 0 otherwise.</returns>
	/// <remarks>This method does not contain branching instructions.</remarks>
	[MethodImpl(MethodImplOptions.AggressiveInlining)]
	public static unsafe byte ToByte(this bool flag)
	{
		// Whenever we need to take the address of an argument, we make a local copy first.
		// This will be removed by the JIT anyway, but it can help produce better codegen and
		// remove unwanted stack spills if the caller is using constant arguments. This is
		// because taking the address of an argument can interfere with some of the flow
		// analysis executed by the JIT, which can in some cases block constant propagation.
		var copy = flag;

		return *(byte*)&copy;
	}

	/// <summary>
	/// Converts the given <see cref="bool"/> value to an <see cref="int"/> mask with
	/// all bits representing the value of the input flag (either 0xFFFFFFFF or 0x00000000).
	/// </summary>
	/// <param name="flag">The input value to convert.</param>
	/// <returns>0xFFFFFFFF if <paramref name="flag"/> is <see langword="true"/>, 0x00000000 otherwise.</returns>
	/// <remarks>
	/// This method does not contain branching instructions, and it is only guaranteed to work with
	/// <see cref="bool"/> values being either 0 or 1. Operations producing a <see cref="bool"/> result,
	/// such as numerical comparisons, always result in a valid value. If the <see cref="bool"/> value is
	/// produced by fields with a custom <see cref="System.Runtime.InteropServices.FieldOffsetAttribute"/>,
	/// or by using <see cref="Unsafe.As{T}"/> or other unsafe APIs to directly manipulate the underlying
	/// data though, it is responsibility of the caller to ensure the validity of the provided value.
	/// </remarks>
	[MethodImpl(MethodImplOptions.AggressiveInlining)]
	public static unsafe int ToBitwiseMask32(this bool flag)
	{
		var copy = flag;
		var rangeFlag = *(byte*)&copy;
		var negativeFlag = rangeFlag - 1;
		var mask = ~negativeFlag;

		return mask;
	}

	/// <summary>
	/// Converts the given <see cref="bool"/> value to a <see cref="long"/> mask with
	/// all bits representing the value of the input flag (either all 1s or 0s).
	/// </summary>
	/// <param name="flag">The input value to convert.</param>
	/// <returns>All 1s if <paramref name="flag"/> is <see langword="true"/>, all 0s otherwise.</returns>
	/// <remarks>This method does not contain branching instructions. See additional note in <see cref="ToBitwiseMask32"/>.</remarks>
	[MethodImpl(MethodImplOptions.AggressiveInlining)]
	public static unsafe long ToBitwiseMask64(this bool flag)
	{
		var copy = flag;
		var rangeFlag = *(byte*)&copy;
		var negativeFlag = (long)rangeFlag - 1;
		var mask = ~negativeFlag;

		return mask;
	}
}